﻿using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

namespace OpenLS.Spreadsheet.Formats.Biff.Cryptography
{
    internal class CAPIProvider
    {
        public static int CreateHandle()
        {
            Instance.CreateInternalHandle(ref Instance._handle, null);
            return Instance._handle;
        }

        private void CreateInternalHandle(ref int handle, string container)
        {
            if (handle == 0)
            {
                lock (this)
                {
                    if (handle == 0 && !_hasError)
                    {
                        int fs = 0, fmk = 0;
                        if (!Environment.UserInteractive && Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 5)
                        {
                            fs = CryptoApi.CRYPT_SILENT;
                            fmk = CryptoApi.CRYPT_MACHINE_KEYSET;
                        }
                        foreach (int t in Providers)
                        {
                            int flags = fs | fmk;
                            if (CryptoApi.CryptAcquireContext(ref handle, container, null, t, flags) == 0)
                            {
                                if (Marshal.GetLastWin32Error() == CryptoApi.NTE_BAD_KEYSET)
                                {
                                    CryptoApi.CryptAcquireContext(ref handle, container, null, t, flags | CryptoApi.CRYPT_NEWKEYSET);
                                }
                                else if (fmk != 0)
                                {
                                    flags = fs;
                                    if (CryptoApi.CryptAcquireContext(ref handle, container, null, t, flags) == 0)
                                    {
                                        if (Marshal.GetLastWin32Error() == CryptoApi.NTE_BAD_KEYSET)
                                        {
                                            CryptoApi.CryptAcquireContext(ref handle, container, null, t, flags | CryptoApi.CRYPT_NEWKEYSET);
                                        }
                                    }
                                }
                            }
                            if (handle != 0)
                                break;
                        }
                        if (handle == 0)
                        {
                            _hasError = true;
                        }
                    }
                    if (_hasError)
                        throw new CryptographicException();
                }
            }
        }
        ~CAPIProvider()
        {
            if (_handle != 0)
                CryptoApi.CryptReleaseContext(_handle, 0);
        }
        private int _handle;
        private bool _hasError;
        private static readonly int[] Providers = new[] { CryptoApi.PROV_RSA_AES, CryptoApi.PROV_RSA_FULL };
        private static readonly CAPIProvider Instance = new CAPIProvider();
    }
}
